#Импорт библиотек
import pandas as pd
import plotly.express as px
from plotly import graph_objects as go
from scipy import stats
import statsmodels.api as sm
from statsmodels.stats.proportion import proportions_ztest
from statsmodels. stats.weightstats import ztest as ztest
Загрузим таблицы
#Импорт данных
marketing_events = pd.read_csv('/datasets/ab_project_marketing_events.csv')
new_users = pd.read_csv('/datasets/final_ab_new_users.csv')
new_events = pd.read_csv('/datasets/final_ab_events.csv')
participants = pd.read_csv('/datasets/final_ab_participants.csv')
Рассмотрим таблицу marketing_events
#Вывод информации о таблице
display(marketing_events)
marketing_events.info()
| name | regions | start_dt | finish_dt | |
|---|---|---|---|---|
| 0 | Christmas&New Year Promo | EU, N.America | 2020-12-25 | 2021-01-03 |
| 1 | St. Valentine's Day Giveaway | EU, CIS, APAC, N.America | 2020-02-14 | 2020-02-16 |
| 2 | St. Patric's Day Promo | EU, N.America | 2020-03-17 | 2020-03-19 |
| 3 | Easter Promo | EU, CIS, APAC, N.America | 2020-04-12 | 2020-04-19 |
| 4 | 4th of July Promo | N.America | 2020-07-04 | 2020-07-11 |
| 5 | Black Friday Ads Campaign | EU, CIS, APAC, N.America | 2020-11-26 | 2020-12-01 |
| 6 | Chinese New Year Promo | APAC | 2020-01-25 | 2020-02-07 |
| 7 | Labor day (May 1st) Ads Campaign | EU, CIS, APAC | 2020-05-01 | 2020-05-03 |
| 8 | International Women's Day Promo | EU, CIS, APAC | 2020-03-08 | 2020-03-10 |
| 9 | Victory Day CIS (May 9th) Event | CIS | 2020-05-09 | 2020-05-11 |
| 10 | CIS New Year Gift Lottery | CIS | 2020-12-30 | 2021-01-07 |
| 11 | Dragon Boat Festival Giveaway | APAC | 2020-06-25 | 2020-07-01 |
| 12 | Single's Day Gift Promo | APAC | 2020-11-11 | 2020-11-12 |
| 13 | Chinese Moon Festival | APAC | 2020-10-01 | 2020-10-07 |
<class 'pandas.core.frame.DataFrame'> RangeIndex: 14 entries, 0 to 13 Data columns (total 4 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 name 14 non-null object 1 regions 14 non-null object 2 start_dt 14 non-null object 3 finish_dt 14 non-null object dtypes: object(4) memory usage: 576.0+ bytes
Приведём столбцы с датой к нужному типу
#Изменение типов данных в столбцах start_dt и finish_dt
marketing_events['start_dt'] = pd.to_datetime(marketing_events['start_dt'])
marketing_events['finish_dt'] = pd.to_datetime(marketing_events['finish_dt'])
marketing_events.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 14 entries, 0 to 13 Data columns (total 4 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 name 14 non-null object 1 regions 14 non-null object 2 start_dt 14 non-null datetime64[ns] 3 finish_dt 14 non-null datetime64[ns] dtypes: datetime64[ns](2), object(2) memory usage: 576.0+ bytes
Рассмотрим таблицу new_users
#Вывод информации о таблице
display(new_users.head(10))
new_users.info()
| user_id | first_date | region | device | |
|---|---|---|---|---|
| 0 | D72A72121175D8BE | 2020-12-07 | EU | PC |
| 1 | F1C668619DFE6E65 | 2020-12-07 | N.America | Android |
| 2 | 2E1BF1D4C37EA01F | 2020-12-07 | EU | PC |
| 3 | 50734A22C0C63768 | 2020-12-07 | EU | iPhone |
| 4 | E1BDDCE0DAFA2679 | 2020-12-07 | N.America | iPhone |
| 5 | 137119F5A9E69421 | 2020-12-07 | N.America | iPhone |
| 6 | 62F0C741CC42D0CC | 2020-12-07 | APAC | iPhone |
| 7 | 8942E64218C9A1ED | 2020-12-07 | EU | PC |
| 8 | 499AFACF904BBAE3 | 2020-12-07 | N.America | iPhone |
| 9 | FFCEA1179C253104 | 2020-12-07 | EU | Android |
<class 'pandas.core.frame.DataFrame'> RangeIndex: 61733 entries, 0 to 61732 Data columns (total 4 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 user_id 61733 non-null object 1 first_date 61733 non-null object 2 region 61733 non-null object 3 device 61733 non-null object dtypes: object(4) memory usage: 1.9+ MB
Приведём столбце с датой к нужному типу
#Изменение типа данных в столбце first_date
new_users['first_date'] = pd.to_datetime(new_users['first_date'])
Проверим наличие пропусков и явных дубликатов
#Проверка наличия дубликатов
print('Явных дубликатов:',new_users.duplicated().sum())
#Проверка наличия пропусков
print('Пропусков', new_users.isna().sum())
Явных дубликатов: 0 Пропусков user_id 0 first_date 0 region 0 device 0 dtype: int64
Проверим неявные дубликаты
#Проверка наличия неявных дубликатов
print('Неявных дубликатов', new_users['user_id'].duplicated().sum())
Неявных дубликатов 0
Убираем пользователей зарегестрировавшихся после даты заданной в ТЗ
#Сортировка по дате
wrong_users = new_users.query('first_date > "2020-12-21"')
#Создания списка с id пользователей зарегестрировавшихся после периода указанного в ТЗ
wrong_users_list = wrong_users['user_id'].tolist()
#Удаление "неправильных" пользователей
new_users = new_users.query('user_id not in @wrong_users_list ')
Рассмотрим таблицу new_events
#Вывод информации о таблице
display(new_events.head(10))
new_events.info()
| user_id | event_dt | event_name | details | |
|---|---|---|---|---|
| 0 | E1BDDCE0DAFA2679 | 2020-12-07 20:22:03 | purchase | 99.99 |
| 1 | 7B6452F081F49504 | 2020-12-07 09:22:53 | purchase | 9.99 |
| 2 | 9CD9F34546DF254C | 2020-12-07 12:59:29 | purchase | 4.99 |
| 3 | 96F27A054B191457 | 2020-12-07 04:02:40 | purchase | 4.99 |
| 4 | 1FD7660FDF94CA1F | 2020-12-07 10:15:09 | purchase | 4.99 |
| 5 | 831887FE7F2D6CBA | 2020-12-07 06:50:29 | purchase | 4.99 |
| 6 | 6B2F726BFD5F8220 | 2020-12-07 11:27:42 | purchase | 4.99 |
| 7 | BEB37715AACF53B0 | 2020-12-07 04:26:15 | purchase | 4.99 |
| 8 | B5FA27F582227197 | 2020-12-07 01:46:37 | purchase | 4.99 |
| 9 | A92195E3CFB83DBD | 2020-12-07 00:32:07 | purchase | 4.99 |
<class 'pandas.core.frame.DataFrame'> RangeIndex: 440317 entries, 0 to 440316 Data columns (total 4 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 user_id 440317 non-null object 1 event_dt 440317 non-null object 2 event_name 440317 non-null object 3 details 62740 non-null float64 dtypes: float64(1), object(3) memory usage: 13.4+ MB
Приведём столбец с датой к нужному типу
#Изменение типа данных в столбце event_dt
new_events['event_dt'] = pd.to_datetime(new_events['event_dt'])
Проверим наличие пропусков и явных дубликатов
#Проверка наличия дубликатов
print('Явных дубликатов:', new_events.duplicated().sum())
#Проверка наличия пропусков
print('Пропусков', new_events.isna().sum())
Явных дубликатов: 0 Пропусков user_id 0 event_dt 0 event_name 0 details 377577 dtype: int64
Рассмотрим неявные дубликаты
#Проверка наличия неявных дубликатов
new_events.duplicated(['user_id','event_dt'])
0 False
1 False
2 False
3 False
4 False
...
440312 True
440313 False
440314 False
440315 True
440316 True
Length: 440317, dtype: bool
Посмотрим на пользователя с примером такого дубликата
#Вывод конкретного пользователя
new_events.query('user_id == "7AEC61159B672CC5"')
| user_id | event_dt | event_name | details | |
|---|---|---|---|---|
| 63003 | 7AEC61159B672CC5 | 2020-12-07 21:26:00 | product_cart | NaN |
| 64457 | 7AEC61159B672CC5 | 2020-12-08 04:18:34 | product_cart | NaN |
| 93598 | 7AEC61159B672CC5 | 2020-12-19 22:13:37 | product_cart | NaN |
| 100887 | 7AEC61159B672CC5 | 2020-12-21 17:22:40 | product_cart | NaN |
| 125201 | 7AEC61159B672CC5 | 2020-12-30 11:36:13 | product_cart | NaN |
| 125727 | 7AEC61159B672CC5 | 2020-12-07 21:26:00 | product_page | NaN |
| 128703 | 7AEC61159B672CC5 | 2020-12-08 04:18:30 | product_page | NaN |
| 187436 | 7AEC61159B672CC5 | 2020-12-19 22:13:38 | product_page | NaN |
| 201812 | 7AEC61159B672CC5 | 2020-12-21 17:22:40 | product_page | NaN |
| 250764 | 7AEC61159B672CC5 | 2020-12-30 11:36:13 | product_page | NaN |
| 251565 | 7AEC61159B672CC5 | 2020-12-07 21:25:59 | login | NaN |
| 256074 | 7AEC61159B672CC5 | 2020-12-08 04:18:30 | login | NaN |
| 344618 | 7AEC61159B672CC5 | 2020-12-19 22:13:37 | login | NaN |
| 366405 | 7AEC61159B672CC5 | 2020-12-21 17:22:38 | login | NaN |
| 440316 | 7AEC61159B672CC5 | 2020-12-30 11:36:13 | login | NaN |
Убираем пользователей зарегестрировавшихся после даты заданной в ТЗ
#Удаление "неправильных" пользователей
new_events = new_events.query('user_id not in @wrong_users_list ')
Рассмотрим таблицу participants
#Вывод информации о таблице
display(participants.head(10))
participants.info()
| user_id | group | ab_test | |
|---|---|---|---|
| 0 | D1ABA3E2887B6A73 | A | recommender_system_test |
| 1 | A7A3664BD6242119 | A | recommender_system_test |
| 2 | DABC14FDDFADD29E | A | recommender_system_test |
| 3 | 04988C5DF189632E | A | recommender_system_test |
| 4 | 482F14783456D21B | B | recommender_system_test |
| 5 | 4FF2998A348C484F | A | recommender_system_test |
| 6 | 7473E0943673C09E | A | recommender_system_test |
| 7 | C46FE336D240A054 | A | recommender_system_test |
| 8 | 92CB588012C10D3D | A | recommender_system_test |
| 9 | 057AB296296C7FC0 | B | recommender_system_test |
<class 'pandas.core.frame.DataFrame'> RangeIndex: 18268 entries, 0 to 18267 Data columns (total 3 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 user_id 18268 non-null object 1 group 18268 non-null object 2 ab_test 18268 non-null object dtypes: object(3) memory usage: 428.3+ KB
#Проверка наличия дубликатов
print('Явные дубликаты:', participants.duplicated().sum())
#Проверка наличия пропусков
print('Пропуски:',participants.isna().sum())
Явные дубликаты: 0 Пропуски: user_id 0 group 0 ab_test 0 dtype: int64
Рассмотрим неявные дубликаты
#Проверка наличия неявных дубликатов
print('Пользователь+группа',participants.duplicated(['user_id','group']).sum())
print('Пользователь+тест',participants.duplicated(['user_id','ab_test']).sum())
Пользователь+группа 826 Пользователь+тест 0
Рассмотрим отдельно пример пользователя
#Вывод конкретного пользователя
participants.query('user_id == "2B0CD24EE4291CA0"')
| user_id | group | ab_test | |
|---|---|---|---|
| 4326 | 2B0CD24EE4291CA0 | B | recommender_system_test |
| 18245 | 2B0CD24EE4291CA0 | B | interface_eu_test |
Создадим таблицу с нужным тестом по ТЗ
#Создание таблицы с тестом из ТЗ
tt_test_users = participants.query('ab_test == "recommender_system_test"')
Убираем пользователей зарегестрировавшихся после даты заданной в ТЗ
#Удаляем "неправильных" пользователей
participants = participants.query('user_id not in @wrong_users_list ')
Создадим таблицу с полными данными по нужному тесту
#Объединяем таблицы
all_data = new_users.merge(participants, on = 'user_id', how = 'left')
all_data = all_data.merge(new_events, on = 'user_id', how = 'outer')
all_data.head(5)
| user_id | first_date | region | device | group | ab_test | event_dt | event_name | details | |
|---|---|---|---|---|---|---|---|---|---|
| 0 | D72A72121175D8BE | 2020-12-07 | EU | PC | A | recommender_system_test | 2020-12-07 21:52:10 | product_page | NaN |
| 1 | D72A72121175D8BE | 2020-12-07 | EU | PC | A | recommender_system_test | 2020-12-07 21:52:07 | login | NaN |
| 2 | F1C668619DFE6E65 | 2020-12-07 | N.America | Android | NaN | NaN | 2020-12-07 16:38:09 | product_page | NaN |
| 3 | F1C668619DFE6E65 | 2020-12-07 | N.America | Android | NaN | NaN | 2020-12-08 02:02:34 | product_page | NaN |
| 4 | F1C668619DFE6E65 | 2020-12-07 | N.America | Android | NaN | NaN | 2020-12-23 14:35:41 | product_page | NaN |
Создадим таблицу с пользователями по нужному тесту из ТЗ
tt_all_data = all_data.query('ab_test == "recommender_system_test"')
tt_all_data.head(5)
| user_id | first_date | region | device | group | ab_test | event_dt | event_name | details | |
|---|---|---|---|---|---|---|---|---|---|
| 0 | D72A72121175D8BE | 2020-12-07 | EU | PC | A | recommender_system_test | 2020-12-07 21:52:10 | product_page | NaN |
| 1 | D72A72121175D8BE | 2020-12-07 | EU | PC | A | recommender_system_test | 2020-12-07 21:52:07 | login | NaN |
| 90 | E6DE857AFBDC6102 | 2020-12-07 | EU | PC | B | recommender_system_test | NaT | NaN | NaN |
| 146 | DD4352CDCF8C3D57 | 2020-12-07 | EU | Android | B | recommender_system_test | 2020-12-07 15:32:54 | product_page | NaN |
| 147 | DD4352CDCF8C3D57 | 2020-12-07 | EU | Android | B | recommender_system_test | 2020-12-08 08:29:31 | product_page | NaN |
Проверим период набора пользователей относительно технического задания
#Выводим границы периода набора пользователей
print('Начало набора пользователей:', tt_all_data['first_date'].min())
print('Конец набора пользователей:', tt_all_data['first_date'].max())
Начало набора пользователей: 2020-12-07 00:00:00 Конец набора пользователей: 2020-12-21 00:00:00
Проверим долю участников теста из региона EU согласно ТЗ
#Считаем пользователей региона EU
eu_users_count = new_users.query('region == "EU"')['user_id'].nunique()
#Создаём список id пользователей из региона EU
eu_id = new_users.query('region == "EU"')['user_id'].tolist()
#Считаем пользователей региона EU учавствующих в тесте
eu_test_users_count = tt_test_users.query('user_id in @eu_id')['user_id'].nunique()
#Считаем долю пользователей из региона EU в тесте
print('Доля участников из региона EU, относительно всех новых пользователей из региона EU:'
,(eu_test_users_count / eu_users_count))
Доля участников из региона EU, относительно всех новых пользователей из региона EU: 0.15
Проверим динамику набора пользователей в группы и равномерность их распределения
#Создаём сводную таблицу с количеством набранных пользователей в группам по дате
dyn_users_group = tt_all_data.pivot_table(index = 'first_date',
columns = 'group',
values = 'user_id',
aggfunc = 'nunique').reset_index()
#Создаём график динамики набора пользователей в группы
fig = px.line(dyn_users_group, x = 'first_date',
y = ['A','B'],
title = 'Динамика набора пользователей в группы',
height=700,
width=1000)
fig.update_layout(legend=dict(title="Группы теста"))
fig.update_xaxes(title = 'Дата')
fig.update_yaxes(title = 'Количество пользователей')
fig.show()
Проверим пересечения с конкурирующим тестом
#Считаем количество пользователей не входящих в нужный тест
print('Количество пользователей учавствущих в двух тестах:',
all_data.groupby('user_id').agg({'ab_test':'nunique'}).query('ab_test > 1')['ab_test'].count())
Количество пользователей учавствущих в двух тестах: 1602
#Таблица с пользователями участниками двух тестов
two_test_users = all_data.groupby('user_id').agg({'ab_test':'nunique'}).query('ab_test > 1')['ab_test'].reset_index()
#Список id пользователей участников двух тестов
two_test_id = two_test_users['user_id'].tolist()
#Удаление пользователей участников двух тестов
tt_all_data = tt_all_data.query('user_id not in @two_test_id')
Проверим есть ли пользователи попавшие в две группы
#Считаем есть ли пользовательские id встречающиеся несколько раз
print('Пользователей входящих в две группы:',
tt_all_data.groupby('user_id').agg({'group':'nunique'}).query('group > 1')['group'].count())
Пользователей входящих в две группы: 0
Проверим период совершения событий пользователями
print('Дата начала совершения событий', tt_all_data['event_dt'].min())
print('Дата конца совершения событий', tt_all_data['event_dt'].max())
Дата начала совершения событий 2020-12-07 00:16:00 Дата конца совершения событий 2020-12-30 06:42:52
print('Пользователей не совершавших событие', tt_all_data['event_name'].isna().sum())
Пользователей не совершавших событие 2311
#Создаём список уникальных событий
event_name = ['product_page', 'login', 'purchase', 'product_cart']
#Таблица с пользователями не совершавшими ни одного события
noevent = tt_all_data.query('event_name not in @event_name')
#Количество пользователей не совершавших события по группам
noevent_group = noevent.groupby('group').agg({'user_id':'nunique'})
noevent_group
| user_id | |
|---|---|
| group | |
| A | 821 |
| B | 1490 |
Удаляем пользователей которые ни разу не авторизовались.
noevent_user_list = noevent['user_id'].tolist()
tt_all_data = tt_all_data.query('user_id not in @noevent_user_list')
Удалим события совершенные пользователями более чем через 14 дня с момента регистрации.
#Создаем столбец с временем прошедшим до события от момента регистрации
tt_all_data['event_time'] = tt_all_data['event_dt'] - tt_all_data['first_date']
#Удалим события совершенные больше чем через 14 дней
tt_all_data = tt_all_data.query('event_time < "14 days 00:00:00"')
#Проверка
tt_all_data.sort_values(by = 'event_time', ascending = False).head(5)
| user_id | first_date | region | device | group | ab_test | event_dt | event_name | details | event_time | |
|---|---|---|---|---|---|---|---|---|---|---|
| 190273 | 6283982213115DF1 | 2020-12-09 | EU | Mac | B | recommender_system_test | 2020-12-22 23:52:52 | login | NaN | 13 days 23:52:52 |
| 190269 | 6283982213115DF1 | 2020-12-09 | EU | Mac | B | recommender_system_test | 2020-12-22 23:52:52 | purchase | 4.99 | 13 days 23:52:52 |
| 174140 | 0B6D6F353F018A4C | 2020-12-15 | EU | Mac | A | recommender_system_test | 2020-12-28 23:49:06 | purchase | 4.99 | 13 days 23:49:06 |
| 174143 | 0B6D6F353F018A4C | 2020-12-15 | EU | Mac | A | recommender_system_test | 2020-12-28 23:49:06 | product_cart | NaN | 13 days 23:49:06 |
| 174146 | 0B6D6F353F018A4C | 2020-12-15 | EU | Mac | A | recommender_system_test | 2020-12-28 23:49:06 | product_page | NaN | 13 days 23:49:06 |
Проверим у всех ли пользователей была возможность учавствовать во всех 14 днях тестирования
#Переменная с концом периода исследования
end_period = pd.to_datetime("2021-01-04")
#Создаём новый столбец с лайв таймом
tt_all_data['life_time'] = end_period - tt_all_data['first_date']
print('Количество пользователей не имевших возможность совершать события все 14 дней:',
tt_all_data.query('life_time < "14 days"')['user_id'].nunique())
Количество пользователей не имевших возможность совершать события все 14 дней: 0
Посмотрим когда пользователи совершают своё первое событие каждого вида
#Добавляем столбец с проверкой на первое совершенное уникальное событие
tt_all_data['first_event'] = tt_all_data.groupby(['user_id', 'event_name'])['event_time'].rank(method='min') == 1
#Создаём таблицу с количеством первых событий по их видам и дням
first_event = tt_all_data.pivot_table(index = (tt_all_data['event_time'].dt.days + 1),
columns = 'event_name',
values = 'first_event',
aggfunc = 'sum').reset_index()
#Выводим таблицу
display(first_event)
#Строим график динамики первых событий
fig = px.line(first_event, x = 'event_time',
y = ['login','product_cart',
'product_page','purchase'],
title = 'Динамика первых событий пользователей')
fig.update_layout(legend = dict(title = 'Событие'))
fig.update_xaxes(title = 'День после регистрации')
fig.update_yaxes(title = 'Количество событий')
fig.show()
| event_name | event_time | login | product_cart | product_page | purchase |
|---|---|---|---|---|---|
| 0 | 1 | 2767 | 822 | 1746 | 845 |
| 1 | 2 | 9 | 1 | 6 | 4 |
| 2 | 3 | 4 | 2 | 2 | 0 |
| 3 | 4 | 2 | 0 | 1 | 0 |
| 4 | 5 | 1 | 0 | 0 | 0 |
| 5 | 6 | 1 | 0 | 0 | 0 |
| 6 | 7 | 2 | 1 | 1 | 1 |
| 7 | 8 | 0 | 0 | 0 | 0 |
| 8 | 9 | 1 | 0 | 1 | 0 |
| 9 | 10 | 0 | 0 | 0 | 0 |
| 10 | 11 | 0 | 0 | 0 | 0 |
| 11 | 12 | 0 | 0 | 0 | 0 |
| 12 | 13 | 0 | 0 | 0 | 0 |
| 13 | 14 | 0 | 0 | 0 | 0 |
Построим гистограмму распределения количества событий на пользователя в разрезе по группам
#Создаём таблицу с подсчетом количества событий для каждого пользователя
events_group = (tt_all_data.groupby(['group','user_id']).size().reset_index()
.rename(columns = {0:'event_count'}))
#Проверка
events_group.sort_values(by = 'user_id').head(5)
| group | user_id | event_count | |
|---|---|---|---|
| 0 | A | 0010A1C096941592 | 12 |
| 1 | A | 003DF44D7589BBD4 | 15 |
| 2 | A | 00505E15A9D81546 | 5 |
| 2082 | B | 005E096DBD379BCF | 4 |
| 3 | A | 006E3E4E232CE760 | 6 |
#Строим гистограмму
fig = px.histogram(events_group,
x = 'event_count',
color = 'group',
title = 'Распределение количества событий на пользователя')
fig.update_layout(legend = dict(title = 'Группа'))
fig.update_xaxes(title = 'Количество событий')
fig.update_yaxes(title = 'Количество пользователей')
fig.show()
print('Среднее значение количества событий на пользователя в группе А',
events_group.query('group == "A"')['event_count'].mean().round(2))
print('Среднее значение количества событий на пользователя в группе B',
events_group.query('group == "B"')['event_count'].mean().round(2))
Среднее значение количества событий на пользователя в группе А 6.89 Среднее значение количества событий на пользователя в группе B 5.4
Построим график динамики количества событий в группах по дням
#Создаём таблицу с количеством событий в группах по дням
day_event = tt_all_data.pivot_table(index = [tt_all_data['event_dt'].dt.date,'group'],
values = 'user_id',
aggfunc = 'count').reset_index()
#Строим график динамики
fig = px.line(day_event,
x = 'event_dt',
y = 'user_id',
color = 'group',
title = 'График динамики количества событий в группах по дням')
fig.update_layout(legend = dict(title = 'Группа'))
fig.update_xaxes(title = 'День события')
fig.update_yaxes(title = 'Количество событий')
fig.show()
Сделаем проверку времени проведения теста с маркетинговыми и другими событиями
#Переменная с началом периода
tt_start = pd.to_datetime("2020-12-07")
#Переменная с концом периода
tt_finish = pd.to_datetime("2021-01-04")
#Автоматизированный срез, нужно лишь ввести в переменные даты по ТЗ
inter_events = marketing_events.query('finish_dt >= @tt_start & start_dt <= @tt_finish')
inter_events
| name | regions | start_dt | finish_dt | |
|---|---|---|---|---|
| 0 | Christmas&New Year Promo | EU, N.America | 2020-12-25 | 2021-01-03 |
| 10 | CIS New Year Gift Lottery | CIS | 2020-12-30 | 2021-01-07 |
Построим таблицу со всеми событиями, которые совершили пользователи.
#Делаем группировку по названию события и группе считая голичество событий
events_funnel = (tt_all_data.groupby(['event_name','group']).agg({'user_id':'count'})
.reset_index().rename(columns = ({'user_id':'count_users'})))
#Проверка
events_funnel
| event_name | group | count_users | |
|---|---|---|---|
| 0 | login | A | 6345 |
| 1 | login | B | 1849 |
| 2 | product_cart | A | 1940 |
| 3 | product_cart | B | 506 |
| 4 | product_page | A | 4126 |
| 5 | product_page | B | 982 |
| 6 | purchase | A | 1929 |
| 7 | purchase | B | 477 |
Пронумеруем последовательность логических действий
#Функция для нумерации логических действий
def funnel_n(ename):
if ename == 'login':
return 1
elif ename == 'product_page':
return 2
elif ename == 'product_cart':
return 3
elif ename == 'purchase':
return 4
#Применяем функцию
events_funnel['funnel_n'] = events_funnel['event_name'].apply(funnel_n)
#Сортируем таблицу по действиям
events_funnel = events_funnel.sort_values(by = 'funnel_n')
#Проверка
events_funnel
| event_name | group | count_users | funnel_n | |
|---|---|---|---|---|
| 0 | login | A | 6345 | 1 |
| 1 | login | B | 1849 | 1 |
| 4 | product_page | A | 4126 | 2 |
| 5 | product_page | B | 982 | 2 |
| 2 | product_cart | A | 1940 | 3 |
| 3 | product_cart | B | 506 | 3 |
| 6 | purchase | A | 1929 | 4 |
| 7 | purchase | B | 477 | 4 |
Построим воронку событий пользователей
#Задаём фигуру
fig = go.Figure()
#Воронка группы А
fig.add_trace(go.Funnel(name = 'A',
y = events_funnel.query('group == "A"')['event_name'],
x = events_funnel.query('group == "A"')['count_users'],
textinfo = "value+percent previous"))
#Воронка группы B
fig.add_trace(go.Funnel(name = 'B',
y = events_funnel.query('group == "B"')['event_name'],
x = events_funnel.query('group == "B"')['count_users'],
textinfo = "value+percent previous"))
fig.update_layout(legend = dict(title = 'Группа'), title = 'Воронка событий совершаемых пользователями')
fig.show()
Оценим разницу долей с помощью z-критерия
#Создаём таблицу с пользователями группы А по таблице сделанной для воронки событий
a_events = events_funnel.query('group == "A"')
#Создаём таблицу с пользователями группы B по таблице сделанной для воронки событий
b_events = events_funnel.query('group == "B"')
Нулевая гипотеза: Между группами А и B нет статистической разницы долей
Альтернативная гипотеза: Между группами А и B есть статистическая разницы долей.
#Задаём уровень статистической значимости
alpha = 0.05
#Проводим z-test
z_stat, p_value = ztest(a_events['count_users'], b_events['count_users'], value = 0)
if p_value < 0.05:
print('Отвергаем нулевую гипотезу')
else:
print('Принимаем нулевую гипотезу')
Отвергаем нулевую гипотезу